/**
//////////////////////////////////
*Node.java
*This class defining classes that represent options of simulations
*model.options.Options
*GPLv3
*@author Ferlin Alessandro
*2012.05.01
//////////////////////////////////
*/

package model.options;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import model.exceptions.StreamException;

public class Options{

	private static Options instance=null;
	
	protected enum protocolType {RED, LSM, ND};
	protected protocolType protocol;
	protected int numSim;
	protected int numNode;
	protected float communicationRadius;
	protected int totEnergy;
	protected int energyForSending;
	protected int energyForReceiving;
	protected int energyForCheckSignature;
	protected float p;
	protected int g;
	
	
	private Options(){resetAllValues();}
	
	/**
	 * Get the protocol of simulations
	 * @return return RED if protocol is Red, or LSM if protocol is Lsm
	 */
	public String getProtocol(){
		if (protocol==protocolType.RED){
			return "RED";
		}
		if (protocol==protocolType.LSM){
			return "LSM";
		}
		return "";
	};
	
	/**
	 * Set Red or Lsm protocol
	 * @param proto can be or RED or LSM
	 * @throws IllegalArgumentException if protocol is not valid
	 * @throws NullPointerException
	 */
	public void setProtocol(String proto) throws IllegalArgumentException, NullPointerException{
		if (proto.equals("ND")) throw new IllegalArgumentException();
		protocol=protocolType.valueOf(proto);
	};
	
	/**
	 * Method of Design-Pattern Singleton, this method return the unique instance of class Options.
	 * @return unique Options instance.
	 */
	public static Options getInstance(){
		if(instance==null)
			instance=new Options();
		return instance;
	};
	
	/**
	 * Number of simulation to be run
	 * @return number of simulations
	 */
	public int getNumSim(){
		return numSim;
	};
	
	/**
	 * Number of nodes in the network
	 * @return number of nodes in the network
	 */
	public int getNumNode(){
		return numNode;
	};
	
	/**
	 * Communication radius of a node as a fraction of the side of the unit-area
	 * @return communication radius
	 */
	public float getCommunicationRadius(){
		return communicationRadius;
	};
	
	/**
	 * Total energy available on a node 
	 * @return Total energy available on a node
	 */
	public int getTotEnergy(){
		return totEnergy;
	};
	
	/**
	 * Energy spent for sending a message
	 * @return Energy spent for sending a message
	 */
	public int getEnergySending(){
		return energyForSending;
	};
	
	/**
	 * Energy spent for receiving a message
	 * @return Energy spent for receiving a message
	 */
	public int getEnergyReceiving(){
		return energyForReceiving;
	};
	
	/**
	 * Energy spent for verifying a signature 
	 * @return Energy spent for verifying a signature 
	 */
	public int getEnergyCheckSign(){
		return energyForCheckSignature;
	};
	
	/**
	 * Probability for a neighbor node to process a location claim
	 * @return Probability for a neighbor node to process a location claim
	 */
	public float getProbOfNeighbor(){
		return p;
	};
	
	/**
	 * Number of destination location
	 * @return Number of destination location
	 */
	public int getNumDestination(){
		return g;
	};
	
	/**
	 * Set number of simulation to be run
	 * @param number of simulations
	 * @throws NumberFormatException if it isn't >= 1
	 */
	public void setNumSim(int numSim) throws NumberFormatException{
		if(numSim<1)
			throw new NumberFormatException("Number of simulations to run is not correct");
		this.numSim=numSim;
	};
	
	/**
	 * Set number of nodes in the network
	 * @param number of nodes in the network
	 * @throws NumberFormatException if it isn't >= 1
	 */
	public void setNumNode(int numNode) throws NumberFormatException{
		if(numNode<1)
			throw new NumberFormatException("Number of node is not correct");
		this.numNode=numNode;
	};
	
	/**
	 * Set communication radius of a node as a fraction of the side of the unit-area
	 * @param communication radius
	 * @throws NumberFormatException if it isn't > 0
	 */
	public void setCommunicationRadius(float communicationRadius) throws NumberFormatException{
		if(communicationRadius>0)
			this.communicationRadius=communicationRadius;
		else
			throw new NumberFormatException("Number of communication radius is not correct");
		
	};
	
	/**
	 * Set total energy available on a node 
	 * @return Total energy available on a node
	 * @throws NumberFormatException if it isn't >= 1
	 */
	public void setTotEnergy(int totEnergy) throws NumberFormatException{
		if(totEnergy<1)
			throw new NumberFormatException("Number of total Energy is not correct");
		this.totEnergy=totEnergy;
	};
	
	/**
	 * Set energy spent for sending a message
	 * @return Energy spent for sending a message
	 * @throws NumberFormatException if it isn't >= 0
	 */
	public void setEnergySending(int energyForSending) throws NumberFormatException{
		if(energyForSending<0)
			throw new NumberFormatException("Number of energy spent for sending a message is not correct");
		this.energyForSending=energyForSending;
	};
	
	/**
	 * Set energy spent for receiving a message
	 * @return Energy spent for receiving a message
	 * @throws NumberFormatException if it isn't >= 0
	 */
	public void setEnergyReceiving(int energyForReceiving) throws NumberFormatException{
		if(energyForReceiving<0)
			throw new NumberFormatException("Number of energy spent for receiving a message is not correct");
		this.energyForReceiving=energyForReceiving;
	};
	
	/**
	 * Set energy spent for verifying a signature 
	 * @return Energy spent for verifying a signature 
	 * @throws NumberFormatException if it isn't >= 0
	 */
	public void setEnergyCheckSign(int energyForCheckSignature) throws NumberFormatException{
		if(energyForCheckSignature<0)
			throw new NumberFormatException("Number of energy spent for verifying a signature is not correct");
		this.energyForCheckSignature=energyForCheckSignature;
	};
	
	/**
	 * Set probability for a neighbor node to process a location claim
	 * @return Probability for a neighbor node to process a location claim
	 * @throws NumberFormatException if it isn't > 0
	 */
	public void setProbOfNeighbor(float probability) throws NumberFormatException{
		if(probability>0)
			this.p=probability;
		else
			throw new NumberFormatException("Probability for a neighbour node to process a location claim is not correct");
	};
	
	/**
	 * Set number of destination location
	 * @return Number of destination location
	 * @throws NumberFormatException if it isn't >= 1
	 */
	public void setNumDestination(int numDest) throws NumberFormatException{
		if(numDest<1)
			throw new NumberFormatException("Number of destination location is not correct");
		this.g=numDest;
	};
	
	/**
	 * Set all values to no valid values
	 */
	public void resetAllValues(){
		protocol=protocolType.ND;
		numSim=0;
		numNode=0;
		communicationRadius=0;
		totEnergy=0;
		energyForSending=-1;
		energyForReceiving=-1;
		energyForCheckSignature=-1;
		p=0;
		g=0;
	};

	/**
	 * fill the options with the data contained in the given file
	 * @param path the path of the file
	 * @throws StreamException if the data contained in the file are not valid
	 */
	public void load(InputStream in) throws StreamException {
		
		try{
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			String[] buffer;
			int row = 0;
			String errors="";
			while(!br.readLine().isEmpty());
			while((line = br.readLine()) != null){
				row++;
				if (line.length()!=0 && line.charAt(0)!='%'){
					
					line=line.replaceAll(" ", "");
					line=line.replaceAll("\t", "");
					buffer=line.split("=");
					
					if (buffer.length == 2){
						
						if (buffer[0].toLowerCase().equals("proto")){
							try{
								setProtocol(buffer[1]);
							}catch (Exception e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in PROTO field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("nsim")){
							try{
								setNumSim(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in NSIM field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("p")){
							try{
								setProbOfNeighbor(Float.parseFloat(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in p field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("g")){
							try{
								setNumDestination(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in g field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("n")){
							try{
								setNumNode(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in n field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("r")){
							try{
								setCommunicationRadius(Float.parseFloat(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in r field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("e")){
							try{
								setTotEnergy(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in E field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("e_send")){
							try{
								setEnergySending(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in E_send field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("e_receive")){
							try{
								setEnergyReceiving(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in E_receive field";
							}
						}
						
						else if (buffer[0].toLowerCase().equals("e_signature")){
							try{
								setEnergyCheckSign(Integer.parseInt(buffer[1]));
							}catch(NumberFormatException e){
								errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect value in E_signature field";
							}
						}
						
						else {
							errors=errors + "\n\tError at line " + String.valueOf(row) + " - this field doesn't exsist";
						}
					}
					
					else{
						errors=errors + "\n\tError at line " + String.valueOf(row) + " - incorrect input format";
					}
				}
				
			}
			//Close the input stream
			in.close();
			if (errors!=""){
				throw new Exception(errors);
			}
		}
		catch (Exception e){//Catch exception if any
			throw new StreamException("Problems with the file: " + e.getMessage());
		}
	}
	
	/**
	 * Return a string made by the chain of all the options (separating with spaces)
	 * @return a String with all the options
	 */
	public String getOutput(){
		return protocol.toString() + " " + numSim + " " + numNode+ " " + communicationRadius + " " + p + " " + g 
		+ " " + totEnergy + " " + energyForSending + " " + energyForReceiving + " " + energyForCheckSignature;
	}
}
